package com.wcs.exam.common.server.upload.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.wcs.exam.common.base.exception.BaseException;
import com.wcs.exam.common.server.upload.Upload;
import com.wcs.exam.common.server.upload.UploadFace;
import com.wcs.exam.common.util.ImgUtil;
import com.wcs.exam.common.util.JsonUtil;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import jakarta.activation.MimetypesFileTypeMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author wcs
 */
@Slf4j
@Component(value = "kodo")
public class KodoUploadImpl implements UploadFace {

    /**
     * 公共读的文件都存入该目录
     */
    private static final String PUBLIC = "public";
    private static final String PRIVATE = "private";

    @Override
    public String uploadPic(MultipartFile file, Upload upload) {
        try {
            String fileName = IdUtil.simpleUUID() + "." + FileUtil.getSuffix(file.getOriginalFilename());
            String filePath = uploadForKodo(upload, PUBLIC, fileName, file.getName(), file.getContentType(), file.getInputStream(), file.getSize());
            return getKodoFileUrl(upload.getKodoDomain(), filePath);
        } catch (Exception e) {
            log.error("七牛云上传错误", e);
        }
        return "";
    }

    @Override
    public String uploadPicBase64(String imgBase64, Upload upload) {
        // 获取图片类型
        String type = ImgUtil.getBase64Type(imgBase64);
        if (StrUtil.isBlank(type)) {
            type = "jpg";
        }
        String fileName = IdUtil.simpleUUID() + "." + type;

        return uploadPicBase64(imgBase64, fileName, upload);
    }

    @Override
    public String uploadPicBase64(String imgBase64, String fileName, Upload upload) {
        try {
            // 解码Base64字符串
            byte[] data = java.util.Base64.getDecoder().decode(imgBase64);

            //  创建字节数组输入流
            InputStream inputStream = new ByteArrayInputStream(data);

            // 上传文件
            MimetypesFileTypeMap mimetypesFileTypeMap = new MimetypesFileTypeMap();
            String contentType = mimetypesFileTypeMap.getContentType(fileName);

            String filePath = uploadForKodo(upload, PUBLIC, fileName, fileName, contentType, inputStream, inputStream.available());
            return getKodoFileUrl(upload.getKodoDomain(), filePath);
        } catch (Exception e) {
            log.error("七牛云上传错误", e);
            return "";
        }
    }

    @Override
    public String uploadDoc(MultipartFile file, Upload upload, Boolean isPublicRead) {
        try {
            String fileName = IdUtil.simpleUUID() + "." + FileUtil.getSuffix(file.getOriginalFilename());
            String filePath = uploadForKodo(upload, isPublicRead ? PUBLIC : PRIVATE, fileName, file.getName(), file.getContentType(), file.getInputStream(), file.getSize());
            return getKodoFileUrl(upload.getKodoDomain(), filePath);
        } catch (Exception e) {
            log.error("七牛云上传错误", e);
        }
        return "";
    }

    @Override
    public String uploadDoc(File file, String fileName, Upload upload, Boolean isPublicRead) {
        try {
            FileInputStream fis = new FileInputStream(file);
            String contentType = new MimetypesFileTypeMap().getContentType(file);
            String newFileName = IdUtil.simpleUUID() + "." + FileUtil.getSuffix(file.getName());
            String filePath = uploadForKodo(upload, isPublicRead ? PUBLIC : PRIVATE, newFileName, fileName, contentType, fis, file.length());
            return getKodoFileUrl(upload.getKodoDomain(), filePath);
        } catch (Exception e) {
            log.error("七牛云上传错误", e);
        }
        return "";
    }

    @Override
    public void delete(String fileUrl, Upload upload) {
        String filePath = fileUrl.replace(upload.getKodoDomain(), "");
        int index = filePath.indexOf("/");
        String bucket = filePath.substring(0, index);
        String object = filePath.substring(index);
        MinioClient minioClient = getKodoClient(upload);
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(object).build());
        } catch (Exception e) {
            log.error("七牛云删除文档失败", e);
            throw new BaseException("删除失败");
        }
    }

    @Override
    public void delete(Collection<String> fileUrls, Upload upload) {
        MinioClient minioClient = getKodoClient(upload);
        String fileDomain = upload.getKodoDomain();
        fileDomain += upload.getKodoBucket() + "/";

        List<DeleteObject> deleteObjectList = new ArrayList<>();
        for (String url : fileUrls) {
            deleteObjectList.add(new DeleteObject(url.replace(fileDomain, "")));
        }
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(upload.getKodoBucket()).objects(deleteObjectList).build());
        try {
            for (Result<DeleteError> result : results) {
                DeleteError deleteError = result.get();
                log.error("Error in deleting bucket {} object {}; {}", deleteError.bucketName(), deleteError.objectName(), deleteError.message());
            }
        } catch (Exception e) {
            log.error("批量删除文件失败", e);
        }
    }

    @Override
    public String getDownloadUrl(String docUrl, int expireSeconds, Upload upload) {
        MinioClient minioClient = getKodoClient(upload);
        String key = docUrl.replace(upload.getKodoDomain() + (upload.getKodoDomain().endsWith(StrUtil.SLASH) ? "" : StrUtil.SLASH), "");
        String url;
        try {
            url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(upload.getKodoBucket()).object(key).expiry(expireSeconds).build());
        } catch (Exception e) {
            log.error("获取失败", e);
            return "";
        }
        url = url.substring(url.indexOf("/") + 1);
        url = url.substring(url.indexOf("/") + 1);
        url = url.substring(url.indexOf("/") + 1);
        url = url.substring(url.indexOf("/") + 1);
        return upload.getKodoDomain() + url;
    }

    @Override
    public String getPreviewConfig(String docUrl, int expireSeconds, Upload upload) {
        if (StringUtils.hasText(upload.getKodoPreviewUrl())) {
            String previewUrl = getKodoFileUrl(upload.getKodoPreviewUrl(), "onlinePreview?url=");
            Map<String, String> map = new HashMap<>();
            map.put("previewUrl", previewUrl + URLEncoder.encode(Base64.encode(getDownloadUrl(docUrl, expireSeconds, upload)), StandardCharsets.UTF_8));
            return JsonUtil.toJsonString(map);
        }
        return "";
    }

    /**
     * 上传文件到MiniIO
     *
     * @param upload   bucket名称
     * @param fileDir  文件目录
     * @param fileName 文件名称
     * @param stream   文件流
     * @return 上传后的文件地址
     * @throws Exception 上传异常
     */
    private String uploadForKodo(Upload upload, String fileDir, String fileName, String fileOriginalName, String contentType, InputStream stream, long objectSize) throws Exception {
        MinioClient minioClient = getKodoClient(upload);

        // 处理前缀目录
        String objectName = StringUtils.hasText(fileDir) ? fileDir + "/" + fileName : fileName;

        // 设置文件下载名称
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileOriginalName, StandardCharsets.UTF_8));
        if (StringUtils.hasText(contentType)) {
            headerMap.put("Content-Type", contentType);
        }

        minioClient.putObject(PutObjectArgs.builder().bucket(upload.getKodoBucket()).object(objectName).headers(headerMap).stream(stream, objectSize, -1).build());
        return objectName;
    }

    private String getKodoFileUrl(String kodoFileDomain, String filePath) {
        if (kodoFileDomain.endsWith("/")) {
            return kodoFileDomain + filePath;
        } else {
            return kodoFileDomain + "/" + filePath;
        }
    }

    /**
     * 创建MinioClient对象
     *
     * @param upload 上传配置
     * @return MinioClient对象
     */
    private MinioClient getKodoClient(Upload upload) {
        MinioClient minioClient = MinioClient.builder().endpoint(upload.getKodoEndpoint()).credentials(upload.getKodoAccessKey(), upload.getKodoSecretKey()).build();
        if (StringUtils.hasText(upload.getKodoBucket())) {
            try {
                if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(upload.getKodoBucket()).build())) {
                    // minioClient.makeBucket(MakeBucketArgs.builder().bucket(upload.getKodoBucket()).build());
                    throw new BaseException("存储桶不存在");
                }

                String bucketPolicy = minioClient.getBucketPolicy(GetBucketPolicyArgs.builder()
                        .bucket(upload.getKodoBucket())
                        .build());
                if (StrUtil.isBlank(bucketPolicy)) {
                    // 设置访问策略
                    String policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\"],\"Resource\":[\"arn:aws:s3:::{bucket}\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListBucket\"],\"Resource\":[\"arn:aws:s3:::{bucket}\"],\"Condition\":{\"StringEquals\":{\"s3:prefix\":[\"{public}/**\"]}}},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::{bucket}/{public}/***\"]}]}\n";
                    minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(upload.getKodoBucket()).config(policy.replace("{bucket}", upload.getKodoBucket()).replace("{public}", PUBLIC)).build());
                }
            } catch (Exception e) {
                throw new RuntimeException("初始化七牛云错误", e);
            }
        }
        return minioClient;
    }

}
